Skip to content

feat(downloader): add SpotiFLAC as a download source#194

Open
pacholoamit wants to merge 3 commits into
LumePart:devfrom
pacholoamit:feat/spotiflac-downloader
Open

feat(downloader): add SpotiFLAC as a download source#194
pacholoamit wants to merge 3 commits into
LumePart:devfrom
pacholoamit:feat/spotiflac-downloader

Conversation

@pacholoamit

@pacholoamit pacholoamit commented Jun 24, 2026

Copy link
Copy Markdown

Adds a spotiflac download service for pulling tracks as lossless FLAC.

Depending on what info Explo has for a track, it uses one of two paths:

  • Spotify-imported playlists already have a track URL, so those go straight through the official spotiflac CLI.
  • Everything else (ListenBrainz discovery, etc.) is looked up by ISRC, with a title/artist search fallback, using a small bundled python helper. Same idea as the existing youtube/ytmusicapi helper.

Both try the configured providers in order (deezer, tidal, qobuz, amazon). Anything that can't be found is skipped, so the other services in DOWNLOAD_SERVICES still get a turn.

To capture the URL for the first path, the Spotify import now reads each track's link from the partner API and carries it through to the downloader.

Config is documented in sample.env: SPOTIFLAC_SOURCES, SPOTIFLAC_QUALITY, SPOTIFLAC_BIN, SPOTIFLAC_PYTHON_PATH, SPOTIFLAC_SCRIPT_PATH, SPOTIFLAC_TIMEOUT, SPOTIFLAC_RETRIES. The docker image installs SpotiFLAC from PyPI (which gives both the CLI and the importable module) and copies in the helper script.

Tested locally with mpd: downloaded FLACs through both paths and checked they decode cleanly with ffmpeg.

Tested with Spotify Playlist imported via Explo

I'm into very obscure music and I KNOW I'M GONNA HATE FOR THIS BUT I LIKE SOME OF THE AI MUSIC especially RnB and spotify's algorithm prioritizing AI artists is getting to me so I want FLAC files. Most of the FLACs I get are from Deezer

image image image

Example logs from Explo when pulling the FLAC file from SpotiFLAC

time=2026-06-26T03:37:12.272+08:00 level=INFO msg="Starting Explo..."
time=2026-06-26T03:37:12.272+08:00 level=INFO msg="Pulling playlist" playlist=custom-ca2ae93d
time=2026-06-26T03:37:12.274+08:00 level=INFO msg="Saved playlist" playlist=custom-ca2ae93d tracks=30
time=2026-06-26T03:37:32.998+08:00 level=INFO msg="download finished" service=spotiflac track="UP DOWN - Dyce.flac" source=deezer
time=2026-06-26T03:37:47.277+08:00 level=INFO msg="download finished" service=spotiflac track="Champagne - Pitched Up Edit - Nic Dean.flac" source=deezer
time=2026-06-26T03:37:54.695+08:00 level=INFO msg="download finished" service=spotiflac track="Don't Rush - Slowed - Jakk Rinse.flac" source=deezer
time=2026-06-26T03:38:19.966+08:00 level=INFO msg="download finished" service=spotiflac track="I am in love - Jagganant George.flac" source=deezer
time=2026-06-26T03:38:22.022+08:00 level=INFO msg="download finished" service=spotiflac track="Chasing You - drill clinton.flac" source=qobuz
time=2026-06-26T03:38:44.967+08:00 level=INFO msg="download finished" service=spotiflac track="Outside - Daniel J.flac" source=qobuz
time=2026-06-26T03:38:55.112+08:00 level=INFO msg="download finished" service=spotiflac track="Officially Missing You - AYA MUSIC.flac" source=deezer
time=2026-06-26T03:39:09.302+08:00 level=INFO msg="download finished" service=spotiflac track="Only One I See - Iamkritical.flac" source=qobuz
time=2026-06-26T03:39:26.265+08:00 level=WARN msg="[spotiflac] no download for 'Can’t Stop the Feelin’ - Storch Blaize, Rico': deezer: No matching track on Deezer (ISRC lookup and text search both failed).; tidal: [tidal] TRACK_NOT_FOUND: Track not found for: explo; qobuz: [qobuz] UNAVAILABLE: All stream APIs failed; amazon: Unexpected: Could not resolve Amazon URL for explo via any method."
time=2026-06-26T03:39:28.351+08:00 level=INFO msg="download finished" service=spotiflac track="Don't Rush - Rocky Bowman.flac" source=qobuz
time=2026-06-26T03:39:35.077+08:00 level=INFO msg="download finished" service=spotiflac track="Emotionally Unavailable - Niykee Heaton.flac" source=deezer
time=2026-06-26T03:39:50.669+08:00 level=INFO msg="download finished" service=spotiflac track="Narcissist - Jon Blankenship.flac" source=deezer
time=2026-06-26T03:40:01.963+08:00 level=INFO msg="download finished" service=spotiflac track="DO WHAT I LIKE - DO WHAT I WANT, Russ Coson, Darrell Medellin, Kiyomi.flac" source=deezer
time=2026-06-26T03:40:07.183+08:00 level=WARN msg="[spotiflac] no download for 'When You Loved Too Hard - D'PRABU': deezer: No matching track on Deezer (ISRC lookup and text search both failed).; tidal: [tidal] TRACK_NOT_FOUND: Track not found for: explo; qobuz: [qobuz] UNAVAILABLE: All stream APIs failed; amazon: Unexpected: Could not resolve Amazon URL for explo via any method."
time=2026-06-26T03:40:19.179+08:00 level=INFO msg="download finished" service=spotiflac track="Go All In - Deanz, Revel Day.flac" source=deezer
time=2026-06-26T03:40:24.520+08:00 level=INFO msg="download finished" service=spotiflac track="Can't Lie - Jade Omari.flac" source=deezer
time=2026-06-26T03:40:54.757+08:00 level=INFO msg="download finished" service=spotiflac track="Everything You Do Is Amazing - D'JASMINE.flac" source=qobuz
time=2026-06-26T03:41:01.045+08:00 level=INFO msg="download finished" service=spotiflac track="Let You Go - Oh well..flac" source=qobuz
time=2026-06-26T03:41:01.715+08:00 level=INFO msg="download finished" service=spotiflac track="Eko - Jae.T.flac" source=qobuz
time=2026-06-26T03:41:18.861+08:00 level=INFO msg="download finished" service=spotiflac track="All That Matters - J.Tajor.flac" source=qobuz
time=2026-06-26T03:41:29.993+08:00 level=INFO msg="download finished" service=spotiflac track="1 In A Million - CALLMEJB.flac" source=qobuz
time=2026-06-26T03:41:35.012+08:00 level=INFO msg="download finished" service=spotiflac track="Hit Eazy - Eric Bellinger, Hitmaka.flac" source=qobuz
time=2026-06-26T03:41:52.870+08:00 level=INFO msg="download finished" service=spotiflac track="LIFELINE - Kodi Williams.flac" source=qobuz
time=2026-06-26T03:42:01.038+08:00 level=INFO msg="download finished" service=spotiflac track="Red Thread (Amo Lead) - Aston Aizen.flac" source=qobuz
time=2026-06-26T03:42:01.639+08:00 level=INFO msg="download finished" service=spotiflac track="don't stop - rl.flac" source=qobuz
time=2026-06-26T03:42:20.321+08:00 level=INFO msg="download finished" service=spotiflac track="Taste - Dylan Wild, Chris Taylor.flac" source=qobuz
time=2026-06-26T03:42:25.984+08:00 level=INFO msg="download finished" service=spotiflac track="Handle Me - Renn Olympus.flac" source=qobuz
time=2026-06-26T03:42:28.652+08:00 level=WARN msg="[spotiflac] no download for 'The Guest - Roman Noir': deezer: No matching track on Deezer (ISRC lookup and text search both failed).; tidal: [tidal] TRACK_NOT_FOUND: Track not found for: explo; qobuz: [qobuz] UNAVAILABLE: All stream APIs failed; amazon: Unexpected: Could not resolve Amazon URL for explo via any method."
time=2026-06-26T03:42:56.507+08:00 level=INFO msg="download finished" service=spotiflac track="Stay With Me - JAMO.flac" source=qobuz
time=2026-06-26T03:43:00.358+08:00 level=INFO msg="download finished" service=spotiflac track="closure - Paige Corwin.flac" source=qobuz
time=2026-06-26T03:43:00.358+08:00 level=WARN msg="[spotiflac] no monitoring required"
time=2026-06-26T03:43:00.442+08:00 level=INFO msg="initiating search" track="The Guest - Roman Noir"
time=2026-06-26T03:43:01.361+08:00 level=INFO msg="initiating search" track="Can’t Stop the Feelin’ - Storch Blaize, Rico"
time=2026-06-26T03:43:02.361+08:00 level=INFO msg="initiating search" track="When You Loved Too Hard - D'PRABU"
time=2026-06-26T03:43:15.485+08:00 level=INFO msg="initiating search" track="The Guest - *oman Noir"
time=2026-06-26T03:43:16.366+08:00 level=INFO msg="initiating search" track="Can’t Stop the Feelin’ - *torch Blaize, Rico"
time=2026-06-26T03:43:32.367+08:00 level=INFO msg="initiating search" track="When You Loved Too Hard - *'PRABU"
time=2026-06-26T03:43:45.501+08:00 level=WARN msg="no results found for query"
time=2026-06-26T03:43:46.370+08:00 level=WARN msg="no results found for query"
time=2026-06-26T03:43:47.369+08:00 level=WARN msg="no results found for query"
time=2026-06-26T03:44:47.377+08:00 level=WARN msg="[slskd/monitor] error fetching download status: no files found to monitor"
time=2026-06-26T03:44:55.283+08:00 level=INFO msg="compiled command: ffmpeg -i /data/Custom-Ca2ae93d/The_Guest-Roman_Noir.m4a.tmp -loglevel error -map 0:a -metadata artist=Roman Noir -metadata title=The Guest -metadata album=The Guest /data/Custom-Ca2ae93d/Roman Noir/The Guest/The Guest.m4a -y"
time=2026-06-26T03:44:55.452+08:00 level=INFO msg="download finished" service=youtube track=The_Guest-Roman_Noir.m4a
time=2026-06-26T03:44:56.379+08:00 level=INFO msg="compiled command: ffmpeg -i /data/Custom-Ca2ae93d/When_You_Loved_Too_Hard-D_PRABU.m4a.tmp -loglevel error -map 0:a -metadata artist=D'PRABU -metadata title=When You Loved Too Hard -metadata album=When You Loved Too Hard /data/Custom-Ca2ae93d/D'PRABU/When You Loved Too Hard/When You Loved Too Hard.m4a -y"
time=2026-06-26T03:44:56.498+08:00 level=INFO msg="download finished" service=youtube track=When_You_Loved_Too_Hard-D_PRABU.m4a
time=2026-06-26T03:44:57.274+08:00 level=INFO msg="compiled command: ffmpeg -i /data/Custom-Ca2ae93d/Can_t_Stop_the_Feelin_-Storch_Blaize,_Rico.m4a.tmp -loglevel error -map 0:a -metadata artist=Storch Blaize, Rico -metadata title=Can’t Stop the Feelin’ -metadata album=After Di Drinks /data/Custom-Ca2ae93d/Storch Blaize/After Di Drinks/Can’t Stop the Feelin’.m4a -y"
time=2026-06-26T03:44:57.389+08:00 level=INFO msg="download finished" service=youtube track=Can_t_Stop_the_Feelin_-Storch_Blaize,_Rico.m4a
time=2026-06-26T03:44:57.389+08:00 level=WARN msg="[youtube] no monitoring required"
time=2026-06-26T03:44:57.448+08:00 level=INFO msg="Refreshing library..." system=subsonic
time=2026-06-26T03:45:27.671+08:00 level=INFO msg="notification sent"
time=2026-06-26T03:45:27.671+08:00 level=INFO msg="playlist created successfully" system=subsonic playlistName="Discover Weekly" notify=true
time=2026-06-26T04:22:25.615+08:00 level=ERROR msg="Failed to read session from store" msg="session not found"
time=2026-06-26T04:29:40.550+08:00 level=INFO msg="successful login" user=pacholoamit

Refs #117

Add a 'spotiflac' download service that fetches lossless FLAC via the
SpotiFLAC python module (deezer/tidal/qobuz/amazon, in priority order),
mirroring the existing youtube_music subprocess pattern. Tracks are resolved
by ISRC with a title/artist search fallback; downloads run synchronously and
degrade gracefully so other DOWNLOAD_SERVICES take over when a track can't be
sourced.

- src/downloader/spotiflac.go: Downloader implementation (no queue monitoring)
- src/downloader/spotiflac/spotiflac_dl.py: bundled wrapper around the module
- src/config/config.go: SPOTIFLAC_* options + DownloadConfig wiring
- src/downloader/downloader.go: register service in factory + needsDownloadDir
- Dockerfile: pip install SpotiFLAC + bundle the wrapper script
- sample.env, src/web/sample.env, README: document the new source
…dule version

- spotiflac_dl.py: use download_track_async (SpotiFLAC >=1.2.1), falling back to
  download_track (<=1.2.0)
- Dockerfile: install the module via a SPOTIFLAC_VERSION build arg (default 1.2.1), pulled
  from the project's matching GitHub version-branch archive since 1.2.1 restored the
  upstream endpoint registry and is not on PyPI yet
- sample.env / src/web/sample.env: document the >=1.2.1 requirement and the build arg

Verified end-to-end: a real FLAC downloads via Deezer through the Go downloader.
…1.2.3

Migrate the spotiflac source to SpotiFLAC 1.2.3, installed from PyPI (which
ships the `spotiflac` CLI alongside the importable module). The downloader now
serves all Explo flows via two paths:

- Tracks with a streaming URL (Spotify-imported playlists) download through the
  official `spotiflac` CLI, matching the exact track. The Spotify import path
  now captures each track's URL (partner API `uri`) and threads it through the
  import -> cache -> run pipeline onto models.Track.SourceURL.
- Tracks matched only by metadata (ISRC or title/artist, e.g. ListenBrainz
  discovery) download through the bundled module helper (spotiflac_dl.py), which
  searches each FLAC provider by ISRC with a title/artist text-search fallback,
  mirroring how the youtube service shells out to ytmusicapi.

Either path tries the configured sources in priority order; tracks SpotiFLAC
cannot source fall through to the other DOWNLOAD_SERVICES.

- Dockerfile installs SpotiFLAC from PyPI and bundles the helper.
- Config: SPOTIFLAC_BIN (CLI), SPOTIFLAC_PYTHON_PATH/SPOTIFLAC_SCRIPT_PATH
  (helper), plus SPOTIFLAC_SOURCES/QUALITY/TIMEOUT/RETRIES.
@pacholoamit pacholoamit marked this pull request as ready for review June 25, 2026 20:43
@nironics

Copy link
Copy Markdown

Awesome work, just confirming that it works for me too. Pretty sweet stuff since using it with a VPN basically removes all rate-limiting 😄

@pacholoamit

Copy link
Copy Markdown
Author

Yeah, please test it out, although the stability is dependant on the SpotifyFlacModule project.. There are times it's unstable but does get fixed pretty quickly by the maintainer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants